package com.whately.core.base.verifycode.util;

import com.whately.core.base.constant.DebugSwitcher;
import com.whately.core.base.constant.WhatelyCoreCacheKeyEnum;
import com.whately.core.base.exception.ServiceError;
import com.whately.core.base.exception.ServiceException;
import com.whately.core.base.model.RedisKeyBuilder;
import com.whately.core.base.utils.AESUtils;
import com.whately.core.base.utils.DateUtil;
import com.whately.core.base.utils.GUID16Utils;
import com.whately.core.base.utils.UUIDUtils;
import com.whately.core.base.verifycode.bo.VerifyImgModel;
import com.whately.core.base.constant.VerifyCodeTypeEnum;
import com.whately.core.base.constant.VerifyGenerateTypeEnum;
import com.whately.core.base.verifycode.kaptcha.CaptchaComp;
import com.whately.core.base.verifycode.slider.SliderComp;
import com.whately.core.base.verifycode.slider.SliderModel;
import com.whately.core.base.verifycode.vo.WhatelyImgVerifyRespVo;
import com.whately.core.base.verifycode.vo.WhatelySliderVerifyRespVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class WhatelyVerifyCodeUtil {
    //短信最多尝试几次
    public static int SMS_TRY_MAX_TIMES = 3;
    //短信默认失效时间
    public static int SMS_DEFAULT_EXPIRE_MINUTES = 2;

    //短信发送次数
    public static int SMS_SEND_COUNT_1m = 2;
    public static int SMS_SEND_COUNT_24h = 30;

    //风控次数
    public static int RISK_CHECK_IP_ACCOUNT = 5;

    public static int RISK_CHECK_ID_ACCOUNT = 2;


    @Autowired
    private DebugSwitcher debugSwitcher;

    @Value("${qcloud.sms.sendSmsFlagInOffLineProfile:false}")
    private boolean sendSmsFlagInOffLineProfile;

    @Value("${img.verify.code.ase.key:12345g789012345h}")
    private String key ;

    @Autowired
    private CaptchaComp captchaComp;

    @Autowired
    private SliderComp sliderComp;

    public String smsCachePrefix(){
        WhatelyCoreCacheKeyEnum cke=WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE_SMS;
        return cke.getCacheKey();
    }

    //不用长度为32的key，改用16
    public  String generateKey(String salt){
        return key.substring(3,11)+salt.substring(5,13);
    }


    //补足16位
    public String suppleRiskIdToLength16(String riskId){
        if(riskId.length()>=16){
            return riskId;
        }
        return StringUtils.leftPad(riskId+key,16,"0");
    }

    /********************************************************
     *  获取图片验证码 - 滑块
     *******************************************************/
    public WhatelySliderVerifyRespVo getSliderImg(Integer type){
        VerifyCodeTypeEnum verifyCodeType = VerifyCodeTypeEnum.getVerifyCodeType(type);
        if(verifyCodeType==null){
            log.error("getSliderImg verifyCodeType is null ,type {}",type);
            throw new ServiceException(ServiceError.REQUEST_PARAMETER_ERROR);
        }

        SliderModel sliderModel = sliderComp.getImg();
        String value = String.valueOf(sliderModel.getYPrecent()) +"&"+ String.valueOf(sliderModel.getXPrecent());

        //不缓存起来，加密 10位数字
        long createTs = DateUtil.nowMilliSecond();
        String text = value+"-"+createTs+"-"+verifyCodeType.getValue()+"-"+ VerifyGenerateTypeEnum.local_slider.getValue();

        String salt = GUID16Utils.getGUID();

        //加密后的密文
        String enStr= AESUtils.encrypt(generateKey(salt),text);

        String tokenID = salt+enStr;

        WhatelySliderVerifyRespVo vo= new WhatelySliderVerifyRespVo();
        vo.setToken(tokenID);
        vo.setSliderImgOri("data:image/jpg;base64,"+ Base64.encodeBase64String(sliderModel.getBackgroundImages()));
        vo.setSliderImgMask("data:image/jpg;base64,"+Base64.encodeBase64String(sliderModel.getMaskImages()));
        return vo;
    }

    /********************************************************
     *  获取图片验证码
     *******************************************************/

    public WhatelyImgVerifyRespVo getImgVerifyCodeRespVO(Integer type) {
        VerifyCodeTypeEnum verifyCodeType = VerifyCodeTypeEnum.getVerifyCodeType(type);
        if(verifyCodeType==null){
            log.error("getImgVerifyCode verifyCodeType is null ,type {}",type);
            throw new ServiceException(ServiceError.REQUEST_PARAMETER_ERROR);
        }

        VerifyImgModel captchaModel = captchaComp.getCaptcha();
        String value = captchaModel.getCaptchaValue();

        //不缓存起来，加密 10位数字
        long createTs = DateUtil.nowMilliSecond();
        String text = value+"-"+createTs+"-"+verifyCodeType.getValue();

        String salt = GUID16Utils.getGUID();

        //加密后的密文
        String enStr= AESUtils.encrypt(generateKey(salt),text);
        String tokenID = salt+enStr;

        WhatelyImgVerifyRespVo respVo = new WhatelyImgVerifyRespVo();
        respVo.setImg(captchaModel.getImg());
        respVo.setToken(tokenID);
        return respVo;
    }


    /********************************************************
     *  验证token from aes
     *
     *******************************************************/
    public void checkImgTokenValue(VerifyCodeTypeEnum verifyCodeType,String token,String riskUniqueId,String imgVerifyCode){
        try{
            String salt=token.substring(0,16);
            String enStr= token.substring(16);
            String deStr= AESUtils.decrypt(this.generateKey(salt),enStr);
            String[] deStrAry=deStr.split("-");
            log.info("checkImgVerifyCodeFromAes  verifyCodeType {} encrypt str {} ,decrypt str {} ",verifyCodeType,enStr,deStr);
            String value=deStrAry[0];
            long  createTs=Long.valueOf(deStrAry[1]);
            Integer type=Integer.valueOf(deStrAry[2]);

            VerifyGenerateTypeEnum generateTypeEnum = VerifyGenerateTypeEnum.local_text;
            if(deStrAry.length==4){
                Integer generateType=Integer.valueOf(deStrAry[3]);
                if(generateType.equals(VerifyGenerateTypeEnum.local_slider.getValue())){
                    generateTypeEnum = VerifyGenerateTypeEnum.local_slider;
                }
            }

            //如果是风控类型，value=riskId+value，需要解密，然后重设value
            if(VerifyCodeTypeEnum.risk_check.getValue().equals(type)){
                //解密imgVerifyCode
                imgVerifyCode= AESUtils.decrypt(this.generateKey(this.suppleRiskIdToLength16(riskUniqueId)),imgVerifyCode);
                //imgVerifyCode=riskId+imgVerifyCode , 替换riskId，只保留imgVerifyCode
                imgVerifyCode=imgVerifyCode.replaceFirst(riskUniqueId,"");
            }else if(!verifyCodeType.getValue().equals(type)){
                log.error("checkImgVerifyCodeFromAes  verifyCodeType type not correct unit {},type {},param type {}",WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE,type,verifyCodeType);
                throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"图形验证码错误，请求类型错误");
            }
            long  expireTs;
            if(WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE.getTimeUnit()== TimeUnit.MINUTES){
                expireTs = createTs+WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE.getDefaultExpireTime()*60*1000;
            }else if(WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE.getTimeUnit()== TimeUnit.SECONDS){
                expireTs = createTs+WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE.getDefaultExpireTime()*1000;
            }else{
                log.error("checkImgVerifyCodeFromAes  not support time  unit {}",WhatelyCoreCacheKeyEnum.TOKEN_VERIFY_CODE);
                throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"图形验证码错误");
            }
            long nowTs= DateUtil.nowMilliSecond();
            if(nowTs<createTs||nowTs>expireTs){
                log.error("checkImgVerifyCodeFromAes create ts {} expire ts {} ,now ts ",createTs ,expireTs ,nowTs);
                throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"图形验证码已过期");
            }

            switch (generateTypeEnum){
                case local_text:
                    if(!imgVerifyCode.equals(value)){
                        log.error("checkImgVerifyCodeFromAes error code ,verifyCode {} ,value {} ",imgVerifyCode ,value );
                        throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"图形验证码错误");
                    }
                    break;
                case local_slider:
                    log.info("checkImgVerifyCodeFromAes slider value in redis is {},front imgVerifyCode {}",value,imgVerifyCode);

                    String[] xyInRedis = value.split("&");
                    String[] xyInFront = imgVerifyCode.split("&");

                    if(Math.abs(Double.parseDouble(xyInFront[0]) - Double.parseDouble(xyInRedis[0]))>3
                            ||Math.abs(Double.parseDouble(xyInFront[1]) - Double.parseDouble(xyInRedis[1]))>1.5){
                        log.error("checkImgVerifyCodeFromAes slider error code ,value in redis  {} ,front imgVerifyCode {} ",value ,imgVerifyCode );
                        throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"图形验证码错误");
                    }
                    break;
                default:
                    throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"验证码错误");
            }
        }catch (ServiceException se){
            throw se;
        }catch (Exception e){
            log.error("checkImgVerifyCodeFromAes error ",e );
            throw new ServiceException(ServiceError.VERIFY_CODE_SMS_ERROR.getCode(),"图形验证码错误");
        }
    }


    /********************************************************
     *  生成验证码的map
     *
     *******************************************************/
    public SmsGenerateResult generateSmsCodeInfo() {
        SmsGenerateResult result=new SmsGenerateResult();

        //生成6位验证码，发送到手机
        Random random = new Random();
        String smsCode = StringUtils.leftPad(Integer.toString(random.nextInt(999999)), 6, "0");

        //调用短信接口，发送标志
        boolean sendFlag = true;

        //测试环境暂时不发短信，验证码固定
        if (debugSwitcher.isProfileLocalOrDev()) {
            smsCode = "445566";
            if (!sendSmsFlagInOffLineProfile) {
                sendFlag = false;
            }

        }

        result.setSmsCode(smsCode);
        result.setToken(UUIDUtils.randomUUID());
        result.setSendFlag(sendFlag);
        return result;
    }


    public RedisKeyBuilder smsCacheToken(VerifyCodeTypeEnum verifyCodeTypeEnum, String token){
        String prefix = smsCachePrefix();
        RedisKeyBuilder rkb =  RedisKeyBuilder.newInstance().appendFixed(prefix).appendVarWithMH(verifyCodeTypeEnum.getValue()).appendVar(token);
        return rkb;
    }
}
